package org.xserver.component.redis;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xserver.component.redis.util.RedisConstant;
import org.xserver.component.redis.util.RedisUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.Tuple;

/**
 * The redisTemplate support all basic operations by provide {@link Jedis} as
 * instance variable, and also provide some wrap operations. If the template not
 * support you business, you can use jedisPool to get jedis instance and do with
 * it, it is import that you use jedis close it when finish handler.
 * 
 * <pre>
 * <OL>Redis data Struct:
 * <LI>String:<p>
 * +--hello----string--+
 * |  world            |
 * +-------------------+
 * <LI>List:<p>
 * +--list-key-----list--+
 * |  item               |
 * |  item2              |
 * |  item               |
 * +---------------------+
 * <LI>Set:<p>
 * +--set-key------set--+
 * |  item              |
 * |  item3             |
 * |  item2             |
 * +--------------------+
 * <LI>Hash:<p>
 * +--hash-key------hash--+
 * |----------------------+
 * |  sub-key1  |  value1 |
 * |  sub-key2  |  value2 |
 * +----------------------+
 * <LI>ZSet:<p>
 * +--zset-key------zset--+
 * |----------------------+
 * |  member1  |   728    |
 * |  member2  |   978    |
 * +----------------------+
 * </pre>
 * 
 * @author idoldefine
 * @since 2014/10/08
 * 
 */
public class RedisTemplate implements RedisOperations {
	private static final Logger log = LoggerFactory
			.getLogger(RedisTemplate.class);

	private JedisPool jedisPool;
	
	private RedisConstant redisConstant;

	public RedisTemplate(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

	// ----------------------------String----------------------------
	@Override
	public void setKeyValue(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set(key, value);
			log.debug("type:{} store key:{} value:{} to redis", new Object[] {
					RedisUtil.REDIS_TYPE_STRING, key, value });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public List<Object> setKeyValueSync(String key, String value,
			Integer timeout) {
		Jedis jedis = null;
		List<Object> result = null;
		try {
			jedis = jedisPool.getResource();
			// add sync
			jedis.watch(key);
			Transaction tx = jedis.multi();
			if (timeout != null) {
				tx.setex(key, timeout, value);
			} else {
				tx.set(key, value);
			}
			result = tx.exec();
			// release sync
			jedis.disconnect();
			log.debug("sync store to redis key=" + key + "    valye=" + value);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
		return result;
	}

	
	/**
     * ZADD key score member [[score member] [score member] ...]
     * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
     * score 值可以是整数值或双精度浮点数。
     * 如果 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误。
     */
    public void ZADD(String key,String price) {
    	Jedis jedis = null;
    	try {
	    	jedis = jedisPool.getResource();
	    	String KETLIST = Math.floor(Float.valueOf(price))+"";
	        Map<String, Double> sourceMember = new HashMap<String, Double>();
	        double prices =  Double.valueOf(price);
	        sourceMember.put(key, prices);
	        jedis.zadd(KETLIST, sourceMember);
    	} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}
    /**
     * ZREMRANGEBYSCORE key min max
     * 移除有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
     * 自版本2.1.6开始， score 值等于 min 或 max 的成员也可以不包括在内，详情请参见 ZRANGEBYSCORE 命令。
     */
    public void ZREMRANGEBYSCORE(String price) {
    	Jedis jedis = null;
		try {
			double max =  Double.valueOf(price);
			jedis = jedisPool.getResource();
			String KETLIST = Math.floor(Float.valueOf(price)) + "";
			jedis.zremrangeByScore(KETLIST, max, max);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
    }
    /**
     * ZRANGE key start stop [WITHSCORES]
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递增(从小到大)来排序。
     * 具有相同 score 值的成员按字典序(lexicographical order )来排列。
     * 如果你需要成员按 score 值递减(从大到小)来排列，请使用 ZREVRANGE 命令。
     * 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
     * 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推。
     * 超出范围的下标并不会引起错误。
     * 比如说，当 start 的值比有序集的最大下标还要大，或是 start > stop 时， ZRANGE 命令只是简单地返回一个空列表。
     * 另一方面，假如 stop 参数的值比有序集的最大下标还要大，那么 Redis 将 stop 当作最大下标来处理。
     * 可以通过使用 WITHSCORES 选项，来让成员和它的 score 值一并返回，返回列表以 value1,score1, ..., valueN,scoreN 的格式表示。
     * 客户端库可能会返回一些更复杂的数据类型，比如数组、元组等。
     * <p/>
     * ZREVRANGE key start stop [WITHSCORES]
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。
     * 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
     * 除了成员按 score 值递减的次序排列这一点外， ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
     * @return 
     */
	public Set<Tuple> ZRANGE(String price) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			String KETLIST = Math.floor(Float.valueOf(price)) + "";
			Set<Tuple> tuples = jedis.zrangeWithScores(KETLIST, 0, -1);
			return tuples;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}
	
	/**
     * ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
     * 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
     * 具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的，不需要额外的计算)。
     * 可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count )，注意当 offset 很大时，定位 offset 的操作可能需要遍历整个有序集，此过程最坏复杂度为 O(N) 时间。
     * 可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员，还是将有序集成员及其 score 值一起返回。
     * 该选项自 Redis 2.0 版本起可用。
     * 区间及无限
     * min 和 max 可以是 -inf 和 +inf ，这样一来，你就可以在不知道有序集的最低和最高 score 值的情况下，使用 ZRANGEBYSCORE 这类命令。
     * 默认情况下，区间的取值使用闭区间 (小于等于或大于等于)，你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
     * <p/>
     * ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
     * 返回有序集 key 中， score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。
     * 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order )排列。
     * 除了成员按 score 值递减的次序排列这一点外， ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
	 * @return 
     */
	
    public Set<Tuple> ZRANGEBYSCORE(String price) {
    	Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			double min = Math.floor(Double.valueOf(price));
			double max =  Double.valueOf(price);
			String KETLIST = min + "";
			Set<Tuple> tuples = jedis.zrangeByScoreWithScores(KETLIST, min,max);
			return tuples;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
    }
	public void setKeyValueExpire(byte[] key, byte[] value, int seconds) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.setex(key, seconds, value);
			log.debug("type:{} store key:{} value:{} to redis", new Object[] {
					RedisUtil.REDIS_TYPE_STRING, key, value });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void setKeyValueExpire(String key, String value, int seconds) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.setex(key, seconds, value);
			log.debug("type:{} set key:{} value:{} expire after {} seconds",
					new Object[] { RedisUtil.REDIS_TYPE_STRING, key, value,
							seconds });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void append(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.append(key, value);
			log.debug("type:{} append key:{} value:{}", new Object[] {
					RedisUtil.REDIS_TYPE_STRING, key, value });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public String getValue(String key) {
		log.debug("get key:{} from redis", key);
		String value = null;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.get(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	@Override
	public byte[] getValue(byte[] key) {
		byte[] value = null;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.get(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	// ----------------------------Hash----------------------------
	@Override
	public void hashSet(String key, Map<String, String> hash) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.hmset(key, hash);
			log.debug("type:{} set key:{} value:{}", new Object[] {
					RedisUtil.REDIS_TYPE_HASH, key, hash });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void hashSet(String key, String field, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.hset(key, field, value);
			log.debug("type:{} set key:{} value:{}", new Object[] {
					RedisUtil.REDIS_TYPE_HASH, key, value });
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public Map<String, String> hashGet(String key) {
		log.debug("type:{} get key:{}", new Object[] {
				RedisUtil.REDIS_TYPE_HASH, key });
		Jedis jedis = null;
		Map<String, String> value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.hgetAll(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	@Override
	public String hashGet(String key, String field) {
		log.debug("type:{} get key:{} field:{}", new Object[] {
				RedisUtil.REDIS_TYPE_HASH, key, field });
		Jedis jedis = null;
		String value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.hget(key, field);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	// ----------------------------List----------------------------
	@Override
	public void listLeftPush(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.lpush(key, value);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void listRightPush(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.rpush(key, value);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public String listLeftPop(String key) {
		Jedis jedis = null;
		String value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.lpop(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	@Override
	public String listRightPop(String key) {
		Jedis jedis = null;
		String value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.rpop(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	@Override
	public long listLength(String key) {
		Jedis jedis = null;
		long length = 0;
		try {
			jedis = jedisPool.getResource();
			length = jedis.llen(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return length;
	}

	/**
	 * Set value at index position, Out of range indexes will generate an error.
	 * 
	 * @param key
	 * @param index
	 * @param value
	 */
	@Override
	public void listSet(String key, long index, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.lset(key, index, value);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	/**
	 * If the key not exist, return empty list
	 * 
	 * @param key
	 * @return
	 */
	@Override
	public List<String> listAll(String key) {
		Jedis jedis = null;
		List<String> value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.lrange(key, 0, -1);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	@Override
	public List<String> listRange(String key, long start, long end) {
		Jedis jedis = null;
		List<String> value = null;
		try {
			jedis = jedisPool.getResource();
			value = jedis.lrange(key, start, end);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return value;
	}

	// ----------------------------All----------------------------
	@Override
	public void expire(String key, int seconds) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.expire(key, seconds);
			log.debug("set key:{} expire after {} seconds", key, seconds);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void delete(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.del(key);
			log.debug("delete key:{}", key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	public void delete(byte[] key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.del(key);
			log.debug("delete key:{}", key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public void delete(String... keys) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.del(keys);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public boolean exists(String key) {
		log.debug("exist key:{}", key);
		Jedis jedis = null;
		boolean exist = false;
		try {
			jedis = jedisPool.getResource();
			exist = jedis.exists(key);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return exist;
	}

	@Override
	public Set<String> keys(String pattern) {
		log.debug("match pattern:{}", pattern);
		Jedis jedis = null;
		Set<String> keys = null;
		try {
			jedis = jedisPool.getResource();
			keys = jedis.keys(pattern);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
		return keys;
	}

	@Override
	public void flushDB() {
		log.debug("remove all objects from redis");
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			String code = jedis.flushDB();
			log.debug("the flushDB code is {}", code);
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}

	@Override
	public Long dbSize() {
		log.debug("get database size");
		Jedis jedis = null;
		Long size = 0L;
		try {
			jedis = jedisPool.getResource();
			size = jedis.dbSize();
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}

		return size;
	}
	
	
	public Object doValidate(String... strings) {

		log.debug("validate accesstoken begin----para: String[]=" + strings);
		if (strings == null || strings[0] == null || "".equals(strings[0])) {
			log.debug("the accesstoken is null or empty");
			return RedisConstant.TOKEN_NULL;
		}

		try {
			Map<String, String> result = this.hashGet(strings[0]);
			if (result == null
					|| result.size() == 0) {
				log.debug(
						"this accesstoken error please request a new one, accesstoken: {}",
						strings[0]);
				return RedisConstant.TOKEN_ERROE;
			}

			Date createTime = RedisConstant.parser(result.get(RedisConstant.CREATE_TIME),RedisConstant.DF_yyyyMMddHHmmss);
			Date nowTime = new Date();

			if (nowTime.getTime() - createTime.getTime() >= redisConstant
					.getAccessTokenTimeout() * 1000) {
				log.debug("this accesstoken has timeOut please request a new one,accesstoken:"
						+ strings[0]);
				return RedisConstant.TOKEN_OVERDUE;
			}

		} catch (Exception ex) {
			log.error("jedis occur error", ex);
			return RedisConstant.TOKEN_SYSERROR;
		}

		log.debug("accesstoken validate success" + strings[0]);
		return RedisConstant.TOKEN_SUCCESS;
	}
	
	public RedisConstant getRedisConstant() {
		return redisConstant;
	}

	public void setRedisConstant(RedisConstant redisConstant) {
		this.redisConstant = redisConstant;
	}
}
